﻿Imports System.Text.RegularExpressions
Imports ProjectWIFE.Exceptions
Imports ProjectWIFE.Configuration
Imports ProjectWIFE.TypesOfLine
#Region "DEBUG Properties"
#Const DEBUG_REQEX_SUCCESS_BEEPS = False
#End Region

Public Class INI
 Inherits CollectionBase
#Region "Protected Variables"
 Protected mLines As New List(Of INI_Base)
 Protected m_Config As INI_Configuration
 Protected m_FilePath As String = ""
#End Region


#Region "New"
 Public Sub New(ByVal Config As INI_Configuration, ByVal FileName As String, Optional ByVal AllowOtherFileExtensions As Boolean = False)
  m_Config = Config
  Init()
  Load(FileName, AllowOtherFileExtensions)
 End Sub

 Public Sub New(ByVal Config As INI_Configuration)
  m_Config = Config
  Init()
 End Sub
#End Region
 Dim cmp As New Comparison(Of INI_Parameter)(Function(x As INI_Parameter, y As INI_Parameter) String.Compare(x.key, y.key))

 ''' <summary>
 ''' Reorganises the ini, putting sections & parameters into alphabetical order
 ''' </summary>
 ''' <remarks></remarks>
 Public Sub Tidy()
  Dim s1 = (From l In (From li As INI_Base In mLines Group By li.Section.SectionName Into Group) Order By l.SectionName Ascending)
  Dim s2 = (From l In s1 Let Group = s(l.Group) Select l.SectionName, Group)
  Dim nl As New List(Of INI_Base)
  For Each q In s2
   For Each q2 In q.Group
    nl.Add(q2)
   Next
  Next
  mLines = nl 
 End Sub
 Private Function s(ByVal ie As IEnumerable(Of INI_Base)) As IEnumerable(Of INI_Base)
  Dim re As New List(Of INI_Base)
  Dim rs As New List(Of INI_Section)
  Dim rp As New List(Of INI_Parameter)
  For i As Integer = 0 To ie.Count - 1
   If TryCast(ie(i), INI_Parameter) IsNot Nothing Then
    rp.Add(ie(i))
   ElseIf TryCast(ie(i), INI_Section) IsNot Nothing Then
    rs.Add(ie(i))
   Else

    re.Add(ie(i))
   End If
  Next
  Dim r = From p As INI_Parameter In rp Order By p.Key Select p
  Return rs.Cast(Of INI_Base).Concat(r.Cast(Of INI_Base)).Concat(re)

 End Function


 Public Function AddSectionWithParameters(ByVal SectionName As String, Optional ByVal comment As String = Nothing) As INI_Section
  Dim rt As INI_Section = Nothing
  If TrySection(SectionName, rt) = False Then
   ' Section doesn't exist
   Dim NewIniSection = New INI_Section(SectionName, comment)
   mLines.Add(NewIniSection)
   Return NewIniSection
  Else
   ' Section already exists
   Return Nothing
  End If
 End Function

 Public Function AddSection(ByVal SectionName As String, Optional ByVal comment As String = Nothing) As INI
  Dim rt As INI_Section = Nothing
  If TrySection(SectionName, rt) = False Then
   ' Section doesn't exist
   Dim NewIniSection = New INI_Section(SectionName, comment)
   mLines.Add(NewIniSection)
   Return Me
  Else
   ' Section already exists
   Return Nothing
  End If
 End Function


 ''' <summary>
 ''' 
 ''' </summary>
 ''' <param name="SectionName">Which section?</param>
 ''' <param name="ReturnSection">Where to store the returned INI_Section?</param>
 ''' <returns>A boolean indicating wether the section was found or not.</returns>
 ''' <remarks></remarks>
 Public Function TrySection(ByVal SectionName As String, ByRef ReturnSection As INI_Section) As Boolean
  Dim Successful As Boolean = True
  ReturnSection = Nothing
  Try
   ReturnSection = Me(SectionName)
  Catch ex As Exceptions_SectionNotFound
   ReturnSection = Nothing
   Successful = False
  End Try
  Return Successful
 End Function


 ''' <summary>
 ''' 
 ''' </summary>
 ''' <param name="SectionName">Which section name?</param>
 ''' <value></value>
 ''' <returns>Corrisponding INI_Section object with same Section Name.</returns>
 ''' <remarks>If no sections exist with the section name then it Throw a Exception_SectionNotFound.</remarks>
 Default Public Overloads Property Item(ByVal SectionName As String) As INI_Section
  Get
   If SectionName Is Nothing Then Throw New Exceptions_SectionIsNull("Section Name can not be Null")
   Dim r = (From p In mLines.OfType(Of INI_Section)() Where p.SectionName = SectionName Select p).ToArray
   If r.Count = 1 Then
    Return r(0)
   Else
    Throw New Exceptions_SectionNotFound(String.Format("SectionName [{0}] can not be found.", SectionName))
    Return Nothing
   End If
  End Get
  Set(ByVal value As INI_Section)
   If SectionName Is Nothing Then Exit Property
   For i As Integer = 0 To mLines.Count - 1
    If TypeOf mLines(i) Is INI_Section Then
     If DirectCast(mLines(i), INI_Section).SectionName = SectionName Then
      mLines(i) = value
      Exit For
     End If
    End If
   Next
  End Set
 End Property

#Region "Loading & Saving"


 Private Sub Load(ByVal FileName As String, Optional ByVal AllowOtherEXTs As Boolean = False)
  If My.Computer.FileSystem.FileExists(FileName) Then
   m_FilePath = FileName
   Dim IsDotINIext = My.Computer.FileSystem.GetFileInfo(FileName).Extension.ToUpper = ".INI"
   If IsDotINIext Or AllowOtherEXTs Then
    Dim fs As New System.IO.StreamReader(FileName, Text.Encoding.Default)
    Dim l As String = ""
    Dim n As Object = Nothing
    mLines.Clear()
    cSec = Nothing
    Dim ln As Integer = 0
    While Not fs.EndOfStream
     ln += 1
     l = fs.ReadLine
     n = ParseLine(l, ln)
     Me.mLines.Add(n)
    End While
    fs.Close()
   End If
  End If
 End Sub

 ''' <summary>
 ''' Save the ini as a file.
 ''' </summary>
 ''' <param name="FileName">Where shall I save it?</param>
 ''' <remarks></remarks>
 Public Sub Save(Optional ByVal FileName As String = "")
  If FileName = "" Then FileName = m_FilePath

  ' Create a new Stream Write for filename.
  Dim fs As New IO.StreamWriter(FileName, False)
  Try
   For Each ThisLine In mLines
    ' Is ThisLine an INI_Parameter?
    If TryCast(ThisLine, TypesOfLine.INI_Parameter) IsNot Nothing Then
     Dim d = DirectCast(ThisLine, TypesOfLine.INI_Parameter)
     If d.HasComment Then
      fs.WriteLine("{0} {1} {2} {3} {4}", d.Key, Me.Configuration.KVI, d.Value, Me.Configuration.CI, d.Comment)
     Else
      fs.WriteLine("{0} {1} {2}", d.Key, Me.Configuration.KVI, d.Value)
     End If
     Continue For
    End If
    ' Is ThisLine an INI_Section?
    If TryCast(ThisLine, TypesOfLine.INI_Section) IsNot Nothing Then
     Dim d = DirectCast(ThisLine, TypesOfLine.INI_Section)
     If d.HasComment Then
      fs.WriteLine("[{0}] {1} {2}", ThisLine.Section.SectionName, Me.Configuration.CI, ThisLine.Comment)
     Else
      fs.WriteLine("[{0}]", ThisLine.Section.SectionName)
     End If
     Continue For
    End If
    ' Is ThisLine an INI_Blank?
    If TryCast(ThisLine, TypesOfLine.INI_Blank) IsNot Nothing Then
     Dim d = DirectCast(ThisLine, TypesOfLine.INI_Blank)
     fs.WriteLine()
     Continue For
    End If
    ' Is ThisLine an INI_Base?
    If TryCast(ThisLine, TypesOfLine.INI_Base) IsNot Nothing Then
     Dim d = DirectCast(ThisLine, TypesOfLine.INI_Base)
     If d.HasComment Then
      fs.WriteLine("{0} {1}", Me.Configuration.CI, ThisLine.Comment)
     Else
      fs.WriteLine()
     End If
     Continue For
    End If
   Next
  Catch ex As Exception
   Throw ex
  Finally
   fs.Close()

  End Try



 End Sub

#End Region

#Region "Parsing"


 Protected cSec As INI_Section = Nothing

 Protected Function ParseLine(ByVal l As String, ByVal linenum As Integer) As Object
  If l = "" OrElse l Is Nothing Then
   Console.WriteLine(String.Format("{0} BlankLine [{1}]", linenum, cSec.SectionName))

   Return New INI_Blank((cSec))
  End If
  Dim r = mCR.Match(l)
  Select Case True
   Case r.Groups("Section").Success
#If DEBUG_REQEX_SUCCESS_BEEPS Then
    Beep()
#End If
    Dim re = New INI_Section(r.Groups("SN").Value, If(r.Groups("CommentText").Success, r.Groups("CommentText").Value, Nothing))
    ' Set current section to be this one
    cSec = re
    ' Return INI Section
    Return cSec
   Case r.Groups("KVP").Success
#If DEBUG_REQEX_SUCCESS_BEEPS Then
    Beep()
#End If
    If cSec Is Nothing Then Throw New Exceptions_ParseError_ParameterNoSection("Parsing Error: Parameter isn't in a Section.")
    Dim re = New INI_Parameter(cSec, r.Groups("Key").Value, r.Groups("Value").Value, If(r.Groups("CommentText").Success, r.Groups("CommentText").Value, Nothing))
    cSec.AddParameter(re)
    Return re
   Case r.Groups("CommentText").Success
#If DEBUG_REQEX_SUCCESS_BEEPS Then
    Beep()
#End If
    Dim re = New INI_Base(cSec, r.Groups("CommentText").Value)
    Return re
   Case Else
    Return Nothing
  End Select
 End Function

#Region "REGEX Variables"
 Protected Sub Init()
  mCommentPattern = "(?<CommentText>(?:" & Regex.Escape(Me.Configuration.CI) & mSP & ")[A-Za-z0-9]{0,})"
  mSectionPattern = "(?<Section>" & mSP & "(?:\[)(?<SN>[A-Za-z0-9\-\s]{1,})(?:\])" & mSP & mCommentPattern & "?$" & ")"
  mSectionREGEX = New Regex(mSectionPattern, RegexOptions.IgnorePatternWhitespace + RegexOptions.Multiline + RegexOptions.ExplicitCapture)
  mCommentREGEX = New Regex(mCommentPattern)
  mKVP = "(?<KVP>" & mSP & "^" & mSP & "(?<Key>[A-Za-z0-9_]{1,})" & _
         mSP & Regex.Escape(m_Config.KVI) & _
         mSP & "(?<Value>" & AsQuoted("[A-Za-z0-9-\\/\._:]{0,}") & ")?" & mSP & mCommentPattern & "?$" & ")"
  mKVREGEX = New Regex(mKVP, RegexOptions.IgnorePatternWhitespace + RegexOptions.Multiline + RegexOptions.ExplicitCapture)
  mComLine = "^" & mSP & mCommentPattern & mSP & "$"
  mCR = New Regex(mSectionPattern & "|" & mKVP & "|" & mComLine)
 End Sub
 Protected mCR As Regex
 Protected mComLine = ""
 Protected mKVREGEX As Regex
 Protected mQuoted As String = ""
 Protected mKVP As String = ""
 Protected mSpacesPattern = "(?:\s{0,})"
 Protected mSP As String = mSpacesPattern
 Protected mCommentPattern = ""

 Protected mCommentREGEX As Regex
 Protected mSectionPattern As String = ""
 Protected mSectionREGEX As Regex

#End Region

 Private Function AsQuoted(ByVal v As String) As String
  Return "(\" & ControlChars.Quote & v & "\" & ControlChars.Quote & "|" & v & ")"

 End Function
#End Region

 Public ReadOnly Property Configuration() As INI_Configuration
  Get
   Return m_Config
  End Get
 End Property

 ''' <summary>
 ''' Creates a new instance of the INI Class, with the corrisponding INI_Configuration
 ''' </summary>
 ''' <param name="config"></param>
 ''' <returns></returns>
 ''' <remarks></remarks>
 Shared Function CreateNew(ByVal config As INI_Configuration) As INI
  Return New INI(config)
 End Function
End Class